/*//////////////////////////////////////////////////////////
 /// Demo of MVC implementation in javascript UI layer /////
 /// author - Alex Li //////////////////////////////////////
 //////////////////////////////////////////////////////////*/

/**
 ** Global object to management global varaiables
 **/
var GlobalVar = new Object();
GlobalVar.objectID = 0;
GlobalVar.objectCollection = [];

/**
 ** Request context class use for pass parameters during communicates between M,V,C
 **/
var Request = Class.create();
Request.prototype = {
  initialize: function(config){
    this.config = config;
    this.param = [];
    this.controller = (config.controller)?config.controller:null;
    this.action=(config.action)?config.action:"";
    this.model = (config.model)?config.model:null;
    this.Event = (config.event)?config.event:null;
    this.view = (config.view)?config.view:null;
	this.context = (config.context)?config.context:{};
  }
}

/**
 ** Response context class use for return results to M,V,C
 **/
var Response = Class.create();
Response.prototype = {
  initialize: function(config){
    this.param = [];
    this.config = config;
    this.view = (config.view)?config.view:null;
    this.model = (config.model)?config.model:null;
    this.index = (config.index)?config.index:"";
    this.Event = (config.event)?config.event:null;
    this.view = (config.view)?config.view:null;
	this.context = (config.context)?config.context:{};
  }
}
/**
 ** MVC abstract super class, all M,V,C entity classes should inherit from this class
 ** Desgin Pattern References: 
 ** 1) Observer
 **/
var MVCCore = Class.create();
MVCCore.prototype = {
  /**
   ** Contructor
   **/
  initialize: function(config){
     this.allocateProperty(config);
  },
  
  /**
   ** Prepare to create the instance of class and allocate object id into global variable
   **/
  allocateProperty:function(config){
    this.config = config;
    if(this.preCreateObject) this.preCreateObject();
    if(config.id) this.id = config.id; else this.id = GlobalVar.objectID++;
    GlobalVar.objectCollection[this.id] = {object:this,type:this.type};
    this.observerManager = new ObserverManager();
  },
  
  /**
   ** Add event lisenter for the instance
   **/
  AddObserver:function(event_name,executer,method){
     var observer = new Observer(executer,method);
     this.observerManager.AddObserver(event_name,observer);
  },
  
  /**
   ** Stop observe event
   **/
  StopObserving:function(event_name,executer,method){
     var observer = new Observer(executer,method);
     this.observerManager.StopObserving(event_name,observer);
  },
  
  /**
   ** Trigger event by giving event name
   **/
  NotifyObserver:function(event_name,context){
    this.observerManager.NotifyAllObservers(event_name,context);
  },
  
  /**
   ** ID getter
   **/
  GetId:function(){
	return this.id;
  },
  
  /**
   ** Check if the given object equal to current instance
   **/
  IsEqual:function(object) {
	return this === object;
  }
}

/**
 ** Controller class. 
 ** Abstract super class of all controllers. Controller is used to handle the view/model event and react acrrodingly
 **/
var Controller = Class.create();
Controller.prototype = Class.extend({
  
  /**
   ** Handle request when listening event fired
   **/
  HandleRequest:function(request){
      this.NotifyObserver("pre_handle_request",request);
      var error_context = new Object();
      if(!request){
         error_context.errorMsg = "No request context posted!";
         this.HandleError(error_context);
      }
      if(request.action){
        try{
           eval("if(this.On_"+request.action+") var response = this.On_"+request.action+"(request)");
        }catch(e){
          error_context.errorMsg = "Error on naviagate the request! Action:" + request.action;
          this.HandleError(error_context);
        }
      } else {
        error_context.errorMsg = "Unavaliable Action request!";
        this.HandleError(error_context);
      }
      this.NotifyObserver("post_handle_request",request); 
      this.HandleResponse(response);
  },
  
  /**
   ** Abstract method and will be called after the event handled. You can override this method
   ** to customize how to return the results to view or model
   **/
  HandleResponse:function(response){
  		/*
      this.NotifyObserver("pre_handle_response",response); 
      var error_context = new Object();
      if(!response){
         error_context.errorMsg = "No view context posted!";
         this.HandleError(error_context);
      }
      if(response.view){
        try{
           eval("var view = new "+response.view+"(response)"); 
           view.Render(response);
        }catch(e){
          error_context.errorMsg = "Error on render the view!";
          this.HandleError(error_context);
        }
      } else {
        error_context.errorMsg = "Error on create the view!";
        this.HandleError(error_context);
      }
      this.NotifyObserver("post_handle_response",response); 
      */
  },
  
  /**
   ** Default handler for model refresh event 
   **/
  On_model_refresh:function(context){
      
  },
  
  /**
   ** Default error handler and error event trigger
   **/
  HandleError:function(error_context){
    this.NotifyObserver("controller_error",error_context); 
    alert(error_context.errorMsg);
  }
},MVCCore.prototype);


/**
 ** Abstract super class for model entity class
 ** This class supplies basic data manipulation like sort by, search
 ** You can extend this class by inherit it and add your
 ** Database access logic by overide the Load() method.
 ** You can also attach controller or view object to the model object
 ** so that them can observe the change of model object and react
 **/
var Model = Class.create();
Model.prototype = Class.extend({
  /**
   ** Constructor
   **/
  initialize: function(config){
    this.allocateProperty(config);
    this.Columns = [];
    this.RecordNumber = 0;
    if(config.columns){
        this.SetColumns(config.columns)
    }
    this.JSONStore = new Array();
    this.data = new Array();
  },
  
  /**
   ** Attach a view or controller to this model object and make them observe model refresh event
   **/
  Attach:function(listener){
    this.AddObserver("refresh",listener,listener.On_model_refresh);
  },
  
  /**
   ** Detach view or controller from model
   **/
  Detach:function(listener) {
	// Implementation
	this.StopObserving("refresh", listener, listener.On_model_refresh);
  },
  
  /**
   ** Fire refresh event and notify all observers the model has been changed
   **/
  Refresh:function(){
	this.OnRefresh();
  },
  
  /**
   ** Fire refresh event and notify all observers the model has been changed
   **/
  OnRefresh:function(){
     var context = new Object();
     context.sender = this;
     context.requestAction = "model_refresh";
     this.NotifyObserver("refresh",context);
  },
  
  /**
   ** Initialize the model data structure
   **/
  SetColumns:function(columns){
        for(i=0;i<columns.length;i++){
            this.Columns.push(columns[i].name);
            this.Columns[columns[i].name]={
                index:i,
                name:columns[i].name,
                type:columns[i].type,
                length:columns[i].length
            };
        }
  },
  
  /**
   ** Load data and fire pre-load data event
   **/
  LoadData:function(request){
    this.NotifyObserver("pre_load_data",request);
    this.Load(request);
  },
  
  /**
   ** Load data without firing pre-load data event
   **/
  Load:function(request){

  },
  
  /**
   ** Parse JSON data and fill into inner data array
   **/
  FillStore:function(originalResponse){
    try{
        var response = eval('('+originalResponse.responseText+')');
        this.JSONStore = response;
        this.data = this.JSONStore.data;
        this.RecordNumber = this.JSONStore.data.length;
    }catch(e){ }
    this.NotifyObserver("post_load_data",this);
    this.OnRefresh();
  },
  
  /**
   ** Entry function for sorting model data
   **/
  SortBy:function(column_name,direction_code){
    this.NotifyObserver("pre_sort_data",this);
    setTimeout(this.DoSort.bind(this,column_name,direction_code),0);
  },
  
  /**
   ** Sortor method
   **/
  DoSort:function(column_name,direction_code){
    var direction_code = (direction_code)?direction_code:"ASC";
    var column_index = this.Columns[column_name].index;
    var column_type = this.Columns[column_name].type;
    var data = this.data;
    var sort_fn = function(left,right){
       var result = 0;
       var left_val = left[column_index]
       var right_val = right[column_index]
        if (column_type=="N"){
          left_val = parseFloat(left_val);
          right_val = parseFloat(right_val);  
        }
        if(left_val>right_val){
            result = (direction_code=="ASC")?1:-1;
        }
        if(left_val<right_val){
            result = (direction_code=="ASC")?-1:1;
        }
        return result;
    }
    this.data.sort(sort_fn);
    sort_fn = null;
    this.NotifyObserver("post_sort_data",this);
    this.OnRefresh();
  },
  
  /**
   ** Get a data in specific row and colum
   **/
  Get:function(row_number,column_name){
    var result = "";
    try{
       var column_index = this.Columns[column_name].index;
       result = this.data[row_number][column_index];
    }catch(e){
        
    }
    return result;
  },
  
  /**
   ** Get a specific row
   **/
  GetRow:function(row_number){
    return this.data[row_number];
  },
  
  /**
   ** Set data into model
   **/
  Set:function(row_number,column_name,new_value) {
	try{
       var column_index = this.Columns[column_name].index;
       this.data[row_number][column_index] = new_value;
    }catch(e){
        
    }
  }
},MVCCore.prototype);

/**
 ** Abstract super class of view, you have to define your view according your requirement but all of them
 ** have to inherit from this class to make sure default behaviors can be performed during event fired.
 **/
var View = Class.create();
View.prototype = Class.extend({
  
  /**
   ** Output HTML to browser
   **/
  Render:function(response){
	 if (this.GetDecorator()) {
		this.GetDecorator().Render(response);
		if (this.GetDecorator().BindEvents) this.GetDecorator().BindEvents();
	 } else {
		var html = this._Render(response);
		this.Container.innerHTML = html;
		if (this.BindEvents) this.BindEvents();
	 }
  },
  
  /**
   ** You have to override this method to define what you want to output to browser.
   **/
  _Render:function(response){
     return "";
  },
  
  /**
   ** Default event hander for model refreshed
   **/
  On_model_refresh:function(context){
      this.Render();
  },
  
  /**
   ** Default error handler
   **/
  HandleError:function(error_context){
     
  },
  
  /**
   ** Bind the view to a model
   **/
  BindModel:function(model){
	// If the view has attached to a model, detach it first
	if (this.Model) {
		this.Model.Detach(this);
	}
    this.Model = model;
    this.Model.Attach(this);
  },
  
  /**
   ** Dispath Event to controller
   **/
  DispathEvent:function(event_name,event){
	// Notify observers
  	var context = new Object();
  	context.Event = event;
  	context.ViewObj = this;
  	this.NotifyObserver(event_name,context);
	
	// Let controller handle the event
  	var request = new Request({
  		action:event_name,
  		model:this.Model,
  		event:event,
  		view:this
  	});
  	if(this.Controller) this.Controller.HandleRequest(request);
  },
  
  /**
   ** Set decorator for view
   **/
  SetDecorator:function(decorator) {
	this.decorator = decorator;
  },
  
  /**
   ** Remove decorator from view
   **/
  ClearDecorator: function(){
	this.SetDecorator(null);
  },
  
  /**
   ** Get view decorator
   **/
  GetDecorator: function() {
	return this.decorator;
  },
  
  /**
   ** Bind Events
   **/
  BindEvents:function(){
	var context = new Object();
  	context.ViewObj = this;
  	this.NotifyObserver("BIND_EVENTS",context);
	if (this._BindEvents) {
		this._BindEvents();
	}
  }
},MVCCore.prototype);


/**
 ** View Decorator
 **/
var Decorator = Class.create();
Decorator.prototype = Class.extend({
  /**
   ** Constructor
   **/
  initialize: function(config){
    this.view = config.view;
	this.view.SetDecorator(this);
	if (this.view.Container) {
		this.Container = this.view.Container;
	} else {
		this.Container = config.container;
	}
	this.viewHTML = "";
    this.allocateProperty(config);
  },
  
  BindEvents: function() {
	if (this._BindEvents) {
		this._BindEvents();
	}
	if (this.view.BindEvents) {
		this.view.BindEvents();
	}
  }
},View.prototype);